using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Autofac;
using Autofac.Builder;
using Autofac.Registrars;
using Microsoft.Practices.ServiceLocation;

namespace MetaContainer.Autofac
{
   /// <summary>
   /// Adapter for MetaContainer using Castle's MicroKernel as implementation of <see cref="IMetaContainer"/>.
   /// </summary>
   public sealed class AutofacMetaContainerAdapter : ServiceLocatorImplBase, IMetaContainer
   {
      private IContainer _container;
      private readonly ContainerBuilder _builder = new ContainerBuilder();
      private bool _needsRebuild;

      /// <summary>
      /// Creates new adapter instance for an in-place container.
      /// </summary>      
      public AutofacMetaContainerAdapter() { }

      /// <summary>
      /// Creates new adapter instance for provided container.
      /// </summary>
      /// <param name="container">Container to be wrapped by <see cref="IMetaContainer"/>.</param>
      public AutofacMetaContainerAdapter(IContainer container)
      {
         _container = container;
      }

      protected override object DoGetInstance(Type serviceType, string key)
      {
         EnsureBuildConatiner();
         if (key != null)
            return _container.Resolve(key);
         return _container.Resolve(serviceType);
      }

      protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
      {
         EnsureBuildConatiner();
         var type = typeof(IEnumerable<>).MakeGenericType(serviceType);

         object instance;
         if (_container.TryResolve(type, out instance))
         {
            return ((IEnumerable)instance).Cast<object>();
         }

         return Enumerable.Empty<object>();
      }
      public void Register(Type from, Type to, string name, Scope scope)
      {
         _needsRebuild = true;
         name = name ?? Guid.NewGuid().ToString();
         switch (scope)
         {
            case Scope.Transient:
               _builder.Register(to).As(from).Named(name).FactoryScoped();
               break;
            case Scope.Singleton:
               _builder.Register(to).As(from).Named(name).SingletonScoped();
               break;
            default:
               throw new NotSupportedException("Not supported scope: " + scope);
         }
      }

      public void Register(Type from, Func<string, object> factoryMethod, Scope scope)
      {
         _needsRebuild = true;
         switch (scope)
         {
            case Scope.Transient:
               _builder.Register(x => factoryMethod(null)).As(from).FactoryScoped();
               break;
            case Scope.Singleton:
               _builder.Register(x => factoryMethod(null)).As(from).SingletonScoped();
               break;
            default:
               throw new NotSupportedException("Not supported scope: " + scope);
         }
      }

      public IMetaContainer CreateChildContainer()
      {
         EnsureBuildConatiner();
         var childContainer = _container.CreateInnerContainer();
         return new AutofacMetaContainerAdapter(childContainer);
      }

      private void EnsureBuildConatiner()
      {
         if (_container == null || _needsRebuild)
         {
            _container = _builder.Build();
            _needsRebuild = false;
         }
      }
   }
}